﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace SharpShare.Storage.FileSystem {
    public abstract class FileStorageItem : IStorageItem, IEquatable<FileStorageItem> {
        private string _originalPath;


        public FileStorageItem(FileSystemInfo info, IFileStorageItemFactory factory) {
            this.Info = info;

            if (factory == null) {
                factory = new TrackingFileStorageItemFactory();
            }

            this.StorageItemFactory = factory;

            _originalPath = info.FullName;
        }

        public FileSystemInfo Info { get; private set; }
        public DriveInfo DriveInfo {
            get {
                return new System.IO.DriveInfo(this.Info.FullName[0].ToString());
            }
        }

        public IFileStorageItemFactory StorageItemFactory { get; private set; }

        public bool IsRoot {
            get {
                if (this.StorageItemFactory.Options.RootPath != null) {
                    return PathUtils.IsEqual(this.Info.FullName, this.StorageItemFactory.Options.RootPath);
                }

                return false;
            }
        }

        internal void Relocate(string newPath) {
            if (System.IO.Directory.Exists(newPath)) {
                this.Info = new DirectoryInfo(newPath);
            } else {
                this.Info = new FileInfo(newPath);
            }
        }

        #region IStorageItem Members

        public string Name {
            get {
                if (this.StorageItemFactory.Options.RootName != null) {
                    if (this.Parent == null) {
                        return this.StorageItemFactory.Options.RootName;
                    }
                }

                return PathUtils.UnescapeFilename(this.Info.Name);
            }
        }

        public IStorageContainer Parent {
            get {
                if (this.IsRoot) {
                    return null;
                }

                string directory = PathUtils.GetParentPath(this.Info.FullName);
                if (directory == null) {
                    return null;
                }

                return (FileStorageContainer)this.StorageItemFactory.Create(directory);
            }
        }

        public bool Exists {
            get {
                this.Info.Refresh();
                return this.Info.Exists;
            }
        }

        public StorageItemAttributes Attributes {
            get {
                this.DemandExists();

                try {
                    StorageItemAttributes attrs = StorageItemAttributes.None;

                    this.Info.Refresh();
                    if (!this.IsRoot && this.Info.Attributes.HasFlag(FileAttributes.Hidden)) {
                        attrs |= StorageItemAttributes.Hidden;
                    }

                    return attrs;
                } catch (StorageException) {
                    throw;
                } catch (Exception ex) {
                    throw HandleException(ex, ExceptionAttemptedAction.ReadAttributes);
                }
            }
            set {
                this.DemandExists();

                try {
                    FileAttributes attrs = FileAttributes.Normal;

                    if (value.HasFlag(StorageItemAttributes.Hidden)) {
                        attrs |= FileAttributes.Hidden;
                    }

                    this.Info.Attributes = attrs;
                } catch (StorageException) {
                    throw;
                } catch (Exception ex) {
                    throw HandleException(ex, ExceptionAttemptedAction.WriteAttributes);
                }
            }
        }

        public DateTime DateCreated {
            get {
                this.DemandExists();

                try {
                    this.Info.Refresh();
                    return this.Info.CreationTimeUtc;
                } catch (StorageException) {
                    throw;
                } catch (Exception ex) {
                    throw HandleException(ex, ExceptionAttemptedAction.ReadAttributes);
                }
            }
            set {
                this.DemandExists();

                try {
                    this.Info.CreationTimeUtc = value.ToUniversalTime();
                } catch (StorageException) {
                    throw;
                } catch (Exception ex) {
                    throw HandleException(ex, ExceptionAttemptedAction.WriteAttributes);
                }
            }
        }

        public DateTime DateModified {
            get {
                this.DemandExists();

                try {
                    this.Info.Refresh();
                    return this.Info.LastWriteTimeUtc;
                } catch (StorageException) {
                    throw;
                } catch (Exception ex) {
                    throw HandleException(ex, ExceptionAttemptedAction.ReadAttributes);
                }
            }
            set {
                this.DemandExists();

                try {
                    this.Info.LastWriteTimeUtc = value.ToUniversalTime();
                } catch (StorageException) {
                    throw;
                } catch (Exception ex) {
                    throw HandleException(ex, ExceptionAttemptedAction.WriteAttributes);
                }
            }
        }

        public IStorageDataProvider OpenMetadata(string name, StorageFileAccess access, StorageFileAccess share, StorageFileMode mode) {
            this.DemandExists();

            try {
                Stream stream = this.StorageItemFactory.Options.MetadataProvider.Open(
                    this.Info.FullName, name, access, share, mode);
                if (stream  == null)
                    return null;
                return new StreamStorageDataProvider(stream);
            } catch (StorageException) {
                throw;
            } catch (Exception ex) {
                throw HandleException(ex, ExceptionAttemptedAction.ReadMetadata);
            }
        }

        public IEnumerable<string> ListMetadata() {
            this.DemandExists();

            try {
                return this.StorageItemFactory.Options.MetadataProvider.List(this.Info.FullName);
            } catch (StorageException) {
                throw;
            } catch (Exception ex) {
                throw HandleException(ex, ExceptionAttemptedAction.ReadMetadata);
            }
        }

        public bool RemoveMetadata(string name) {
            this.DemandExists();

            try {
                return this.StorageItemFactory.Options.MetadataProvider.Remove(this.Info.FullName, name);
            } catch (StorageException) {
                throw;
            } catch (Exception ex) {
                throw HandleException(ex, ExceptionAttemptedAction.ReadMetadata);
            }
        }

        public void Delete() {
            this.DemandExists();

            try {
                this.Info.Delete();
            } catch (StorageException) {
                throw;
            } catch (Exception ex) {
                throw HandleException(ex, ExceptionAttemptedAction.Delete);
            }
        }

        public void Move(IStorageContainer container, string newNameOrNull) {
            this.DemandExists();

            FileStorageContainer newParent = (FileStorageContainer)container;

            newParent.DemandExists();

            if (newNameOrNull == null) {
                newNameOrNull = this.Info.Name;
            }

            string oldPath = this.Info.FullName;
            string newPath = PathUtils.Combine(newParent.Info.FullName, PathUtils.EscapeFilename(newNameOrNull));

            try {
                this.Move(newPath);

                this.StorageItemFactory.Relocate(oldPath, newPath);
            } catch (StorageException) {
                throw;
            } catch (Exception ex) {
                throw HandleException(ex, ExceptionAttemptedAction.Move);
            }
        }

        protected abstract void Move(string newPath);

        public Security.StorageAccessList AvailableAccess {
            get { return Security.StorageAccessList.Empty; }
        }

        #endregion

        #region ICastable<IStorageContainer> Members

        IStorageContainer ICastable<IStorageContainer>.TryCast() {
            return (this as IStorageContainer);
        }

        #endregion

        #region ICastable<IStorageFile> Members

        IStorageFile ICastable<IStorageFile>.TryCast() {
            return (this as IStorageFile);
        }

        #endregion

        #region IEquatable<FileStorageItem> Members

        public bool Equals(FileStorageItem other) {
            if (other == null) {
                return false;
            }

            return (PathUtils.IsEqual(this.Info.FullName, other.Info.FullName) && this.StorageItemFactory.Equals(other.StorageItemFactory));
        }

        #endregion

        public override bool Equals(object obj) {
            return Equals(obj as FileStorageItem);
        }

        public override int GetHashCode() {
            return PathUtils.GetHashCode(_originalPath);
        }

        public override string ToString() {
            return string.Format("[FileSystem] {0}", this.Info.FullName);
        }

        public static FileStorageItem Create(string path, IFileStorageItemFactory factory) {
            if (System.IO.Directory.Exists(path)) {
                return new FileStorageContainer(new DirectoryInfo(path), factory);
            }

            if (System.IO.File.Exists(path)) {
                return new FileStorageFile(new FileInfo(path), factory);
            }

            return null;
        }

        internal enum ExceptionAttemptedAction {
            Other,
            Delete,
            Move,
            Rename,
            Open,
            CreateFile,
            CreateContainer,
            ListContents,
            ReadAttributes,
            WriteAttributes,
            ReadMetadata,
            ModifyMetadata
        }

        internal static Exception HandleException(Exception ex, ExceptionAttemptedAction action = ExceptionAttemptedAction.Other) {
            if (ex is UnauthorizedAccessException) {
                throw new StorageItemUnauthorizedException(ex);
            }

            if (ex is DirectoryNotFoundException) {
                throw new StorageContainerNotFoundException(ex);
            }

            if (ex is FileNotFoundException) {
                throw new StorageItemNotFoundException(ex);
            }

            if (ex is IOException) {
                throw new StorageItemInUseException(ex);
            }

            throw new InvalidOperationException("Unhandled exception performing action '" + action.ToString() + "'.", ex);
        }


    }
}
